package cn.edu.cug.cs.gtl.docsrv.shp.manager;

import cn.edu.cug.cs.gtl.common.Pair;
import cn.edu.cug.cs.gtl.docsrv.config.Configuration;
import cn.edu.cug.cs.gtl.docsrv.shp.querier.BlockQuerier;
import cn.edu.cug.cs.gtl.docsrv.shp.querier.DefaultBlockQuerier;
import cn.edu.cug.cs.gtl.docsrv.shp.updater.BlockUpdater;
import cn.edu.cug.cs.gtl.docsrv.shp.updater.DefaultBlockUpdater;
import cn.edu.cug.cs.gtl.io.FileDataSplitter;
import cn.edu.cug.cs.gtl.mybatis.Session;
import cn.edu.cug.cs.gtl.mybatis.mapper.shp.*;
import cn.edu.cug.cs.gtl.mybatis.metadata.ShpUtils;
import cn.edu.cug.cs.gtl.protos.TimeValue;
import cn.edu.cug.cs.gtl.protoswrapper.TimeValueWrapper;
import org.locationtech.jts.geom.Envelope;

import java.io.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 本地文件系统版本的图形库管理器
 * 元数据ShpItem采用Oracle或Sqlite等关系数据库管理，
 * 如果是sqlite3，其数据库文件为docsrv.db，位于本地数据文件夹的根目录下，
 * 图形文件采用本地文件系统进行管理
 */
public class LocalShpManager extends ShpManager{
    /**
     * 构造函数
     *
     * @param session
     */
    public LocalShpManager(Session session) {
        super(session);
        File f = new File(Configuration.getManagerConfig().getLocalShpDataRoot());
        if(!f.exists()){
            f.mkdirs();
        }
        File fTmp = new File(Configuration.getManagerConfig().getLocalDataRoot()+File.separator+"tmp");
        if(!fTmp.exists()){
            fTmp.mkdirs();
        }
    }

    public String getDataRoot(){
        return Configuration.getManagerConfig().getLocalShpDataRoot();
    }
    /**
     * 将ShpUrl解析成localDir和localFileName
     * @param url  格式为  file:/data/tmp/shpExample.zip
     *                    file:D:/data/tmp/shpExample.zip
     * @return 解析后的结果为
     * localDir = /data/tmp
     * localFileName=shpExample.zip
     */
    private static  Pair<String,String> parseShpUrl(String url){
        String[] ss = cn.edu.cug.cs.gtl.io.File.split(url);
        String remoteFileName = ss[ss.length - 1];
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 1; i < ss.length - 1; i++) {
            stringBuilder.append(File.separator);
            stringBuilder.append(ss[i]);
        }

        String remoteDir = stringBuilder.toString();
        //如果是在Windows下，需要处理盘符
        String [] ws = ss[0].split(":");
        if(ws.length>1){
            if(!ws[1].isEmpty())
                remoteDir = ws[1]+":"+remoteDir;
        }
        Pair<String,String> p = new Pair<>(remoteDir,remoteFileName);
        return p;
    }

    /**
     *
     * @param shpItem
     * @param shpFile
     * @return
     * @throws Exception
     */
    protected static boolean writeToFileSystem(ShpItem shpItem, ShpFile shpFile) throws Exception{
        //写入本地文件系统
        Pair<String, String> p = parseShpUrl(shpItem.getShpUrl());
        File f = new File(p.first()+File.separator+p.second());
        OutputStream outputStream = new FileOutputStream(f);
        outputStream.write(shpFile.getShpRawBytes());
        outputStream.close();
        return true;
    }

    /**
     *
     * @param localDir
     * @param localFileName
     * @return
     * @throws Exception
     */
    protected static ShpFile readFromFileSystem(String localDir, String localFileName) throws Exception{
        File f = new File(localDir +File.separator+ localFileName);
        InputStream inputStream = new FileInputStream(f);
        int len = (int)f.length();
        ShpFile shpFile = new ShpFile();
        byte[] bytes = new byte[len];
        inputStream.read(bytes);
        shpFile.setShpRawBytes(bytes);
        inputStream.close();
        return shpFile;
    }

    /**
     * 将一个由shpItem+shpFile组成的图形文档插入数据库
     *
     * @param shpItem 图形元数据信息,
     *                其中的shpUrl一定要设置值，形式如下
     *                file:/data/tmp/shpExample.zip
     * @param shpFile 图形文件内容
     * @return
     */
    @Override
    public boolean insert(ShpItem shpItem, ShpFile shpFile) {
        try {
            writeToFileSystem(shpItem,shpFile);
            //写入元数据
            ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
            shpItemMapper.insert(shpItem);
            session.commit();
            return true;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 将ShpAll分解成ShpItem和ShpFile，
     * 然后将ShpItem插入数据表SHP_ITEM，
     * 将ShpFile插入数据表SHP_FILE，
     * @param shpAll
     * @return
     */
    @Override
    public boolean insert(ShpAll shpAll) {
        Pair<ShpItem,ShpFile> p = ShpUtils.fromShpAll(shpAll);
        return insert(p.getFirst(),p.getSecond());
    }

    /**
     * 读取本地目录中的所有图形文档，将其插入图形库中
     *
     * @param localDir 本地目录
     * @return 成功返回true, 否则返回false
     */
    @Override
    public boolean insert(String localDir) {
        try {

            File file = new File(localDir);
            if (!file.isDirectory())
                return false;
            File parent = file.getParentFile();

            List<ShpCatalog> catalogs = localDirToCatalogs(localDir);
            for(ShpCatalog shpCatalog: catalogs){
                file = new File(parent.getAbsolutePath()+File.separator+shpCatalog.getCatalogName());
                if(!file.exists() || !file.isDirectory())
                    continue;

                File[] files = file.listFiles();
                for (File f : files) {
                    if(f.isDirectory()){
                        continue;
                    }
                    Pair<ShpItem,ShpFile> p = parseShpFiles(f);

                    if(p==null)  continue;

                    //设置SHPItem
                    ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
                    p.first().setShpInternalId(shpItemMapper.maxInternalId().add(BigDecimal.ONE));
                    StringBuilder shpUrl=new StringBuilder();
                    shpUrl.append("file:");
                    shpUrl.append(Configuration.getManagerConfig().getLocalShpDataRoot());
                    shpUrl.append(File.separator);
                    shpUrl.append(f.getName().replace("shp","zip").trim());
                    p.first().setShpUrl(shpUrl.toString());
                    p.first().setShpCatalog(shpCatalog.getCatalogInternalId());
                    shpItemMapper.insert(p.first());
                    session.commit();

                    writeToFileSystem(p.first(),p.second());
                }
            }

        }
        catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 读取本地目录中的所有图形文档，将其插入图形库中
     * 不管Catalog相关信息的处理，直接插入数据库中
     * @param localDir 本地目录
     * @return 成功返回true, 否则返回false
     */
    public boolean insertWithoutCatalog(String localDir) {
        try {

            File file = new File(localDir);
            if (!file.isDirectory())
                return false;
            File[] files = file.listFiles();
            for (File f : files) {
                if(f.isDirectory()){
                    return insert(f.getCanonicalPath());
                }

                Pair<ShpItem,ShpFile> p = parseShpFiles(f);

                if(p==null)  continue;

                //设置SHPItem
                ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
                p.first().setShpInternalId(BigDecimal.valueOf(shpItemMapper.maxInternalId().doubleValue()+1));
                StringBuilder shpUrl=new StringBuilder();
                shpUrl.append("file:");
                shpUrl.append(Configuration.getManagerConfig().getLocalShpDataRoot());
                shpUrl.append(File.separator);
                shpUrl.append(f.getName().replace("shp","zip").trim());
                p.first().setShpUrl(shpUrl.toString());

                shpItemMapper.insert(p.first());
                session.commit();

                writeToFileSystem(p.first(),p.second());
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 根据shpInternalId删除数据库中对应的图形文档记录
     *
     * @param shpItem 必须填充shpInternalId和shpUrl
     * @return
     */
    @Override
    public boolean delete(ShpItem shpItem) {
        try{
            ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
            shpItemMapper.deleteByPrimaryKey(shpItem.getShpInternalId());
            session.commit();
            Pair<String,String> p = parseShpUrl(shpItem.getShpUrl());
            File f = new File(p.first()+File.separator+p.second());
            if(f.exists())
                f.delete();
            return true;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 根据Id删除图形库中的元数据和内容数据
     * @param shpAll 必须填充shpInternalId和shpUrl
     * @return
     */
    @Override
    public boolean delete(ShpAll shpAll) {
        Pair<ShpItem,ShpFile> p = ShpUtils.fromShpAll(shpAll);
        return delete(p.first());
    }

    /**
     * 根据shpItem+shpFile更新数据库中的图形文档记录
     *
     * @param shpItem 该对象的shpInternalId必须填充,
     *                shpUrl必须填充
     * @param shpFile 更新文档文件的内容
     * @return 更新成功返回true, 否则返回false
     */
    @Override
    public boolean update(ShpItem shpItem, ShpFile shpFile) {
        try{
            ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
            shpItemMapper.updateByPrimaryKey(shpItem);

            Pair<String,String> p = parseShpUrl(shpItem.getShpUrl());
            File f = new File(p.first()+File.separator+p.second());
            boolean b = f.exists();
            if(b) f.delete();
            return writeToFileSystem(shpItem,shpFile);
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    /**
     *根据ShpAll中的内容更新图形库中对应的信息
     * @param shpAll 该对象的shpInternalId必须填充
     * @return
     */
    @Override
    public boolean update(ShpAll shpAll) {
        Pair<ShpItem,ShpFile> p = ShpUtils.fromShpAll(shpAll);
        return update(p.first(),p.second());
    }

    /**
     * 根据ID查询图形块信息
     * @param shpInternalId
     * @return
     */
    @Override
    public ShpAll queryByInternalId(BigDecimal shpInternalId) {
        try{
            ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
            ShpItem shpItem = shpItemMapper.selectByPrimaryKey(shpInternalId);
            Pair<String,String> p = parseShpUrl(shpItem.getShpUrl());


            ShpFile shpFile = readFromFileSystem(p.first(),p.second());
            shpFile.setShpInternalId(shpInternalId);

            ShpAll shpAll = ShpUtils.toShpAll(shpItem,shpFile);
            return shpAll;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据英文标题查询图形内容
     * @param shpEnTitle
     * @return
     */
    @Override
    public List<ShpAll> queryByEnTitle(String shpEnTitle){
        List<ShpAll> res = new ArrayList<>();
        try{
            ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
            List<ShpItem> shpItems = shpItemMapper.selectByEnTitle(shpEnTitle);
            for(ShpItem shpItem: shpItems){
                Pair<String,String> p = parseShpUrl(shpItem.getShpUrl());
                ShpFile shpFile = readFromFileSystem(p.first(),p.second());
                shpFile.setShpInternalId(shpItem.getShpInternalId());

                ShpAll shpAll = ShpUtils.toShpAll(shpItem,shpFile);
                res.add(shpAll);
            }
            return res;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据中文标题查询图形内容
     * @param shpCnTitle  文档注释
     * @return
     */
    @Override
    public List<ShpAll> queryByComment(String shpCnTitle) {
        List<ShpAll> res = new ArrayList<>();
        try{
            ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
            List<ShpItem> shpItems = shpItemMapper.selectByCnTitle(shpCnTitle);
            for(ShpItem shpItem: shpItems){
                Pair<String,String> p = parseShpUrl(shpItem.getShpUrl());
                ShpFile shpFile = readFromFileSystem(p.first(),p.second());
                shpFile.setShpInternalId(shpItem.getShpInternalId());

                ShpAll shpAll = ShpUtils.toShpAll(shpItem,shpFile);
                res.add(shpAll);
            }
            return res;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据类型查询图形信息
     * @param shpType
     * @return
     */
    @Override
    public List<ShpAll> queryByType(String shpType) {
        List<ShpAll> res = new ArrayList<>();
        try{
            ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
            List<ShpItem> shpItems = shpItemMapper.selectByType(shpType);
            for(ShpItem shpItem: shpItems){
                Pair<String,String> p = parseShpUrl(shpItem.getShpUrl());
                ShpFile shpFile = readFromFileSystem(p.first(),p.second());
                shpFile.setShpInternalId(shpItem.getShpInternalId());
                ShpAll shpAll = ShpUtils.toShpAll(shpItem,shpFile);
                res.add(shpAll);
            }
            return res;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据添加入库时间查询图形
     * @param begin 开始时间
     * @param end  结束时间
     * @return
     */
    @Override
    public List<ShpAll> queryByAppendDate(TimeValue begin, TimeValue end) {
        List<ShpAll> res = new ArrayList<>();
        try{
            ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
            List<ShpItem> shpItems =  shpItemMapper.selectByAppendDate(TimeValueWrapper.toUtilDate(begin),TimeValueWrapper.toUtilDate(end));
            for(ShpItem shpItem: shpItems){
                Pair<String,String> p = parseShpUrl(shpItem.getShpUrl());
                ShpFile shpFile = readFromFileSystem(p.first(),p.second());
                shpFile.setShpInternalId(shpItem.getShpInternalId());
                ShpAll shpAll = ShpUtils.toShpAll(shpItem,shpFile);
                res.add(shpAll);
            }
            return res;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据传入参数在图形库中进行查询
     *       try {
     *             Map<String,Object> p = new LinkedHashMap<String,Object>();
     *             p.put("shpInternalId",BigDecimal.valueOf(1));
     *             List<ShpItem> r1 = this.query(p);
     *             p.clear();
     *             p.put("shpEnTile","aaa");
     *             p.put("shpCnTile","zhangsan");
     *             List<ShpItem> r2 = this.query(p);
     *             boolean b = r1.size()==r2.size();
     *         }
     *         catch (Exception e){
     *             e.printStackTrace();
     *         }
     * @param parameters
     *     BigDecimal shpInternalId;
     *
     *     String shpEnTitle;
     *
     *     String shpCnTitle;
     *
     *     String shpUuid;
     *
     *     String shpVersion;
     *
     *     String shpKeywords;
     *
     *     String shpAbstract;
     *
     *     String shpContent;
     *
     *     String shpType;
     *
     *     String shpAuthors;
     *
     *     String shpAffiliation;
     *
     *     String shpTags;
     *
     *     String shpUrl;
     *
     *     Date shpAppendDate;
     *
     *     Date shpUpdateDate;
     *
     *     Date shpCreateDate;
     *
     *     String shpMemo;
     *
     *     BigDecimal shpCatalog;
     *
     *     String shpExtent;
     *
     *     byte[] shpRawBytes;
     * @return 返回查询到的文档。
     */
    @Override
    public List<ShpAll> query(Map<String, Object> parameters) {

        List<ShpAll> res = new ArrayList<>();
        try{
            ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
            List<ShpItem> shpItems =  shpItemMapper.query(parameters);
            for(ShpItem shpItem: shpItems){
                Pair<String,String> p = parseShpUrl(shpItem.getShpUrl());
                ShpFile shpFile = readFromFileSystem(p.first(),p.second());
                shpFile.setShpInternalId(shpItem.getShpInternalId());
                ShpAll shpAll = ShpUtils.toShpAll(shpItem,shpFile);
                res.add(shpAll);
            }
            return res;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取图形块ShapeBlock级别的查询器
     *
     * @return
     */
    @Override
    public BlockQuerier getBlockQuerier() {
        return new DefaultBlockQuerier(this);
    }

    /**
     * 获取图形块ShapeBlock级别的更新器
     * @return
     */
    @Override
    public BlockUpdater getBlockUpdater() {
        return new DefaultBlockUpdater(this);
    }

    /**
     * 查询得到所有ShapeBlock的边界矩形
     * @return
     */
    @Override
    public  List<Pair<BigDecimal, Envelope>> queryAllEnvelopes(){
        try {
            ShpItemMapper shpItemMapper = session.getMapper(ShpItemMapper.class);
            List<LinkedHashMap<String, Object>> ls = shpItemMapper.selectAllInternalIdAndExtents();
            System.out.println(ls.size());
            List<Pair<BigDecimal, Envelope>> r = new ArrayList<>();
            for (LinkedHashMap<String, Object> l: ls){
                BigDecimal id = (BigDecimal)l.get("SHP_INTERNAL_ID");
                String coords = (String)l.get("SHP_EXTENT");
                String [] cs = coords.split(FileDataSplitter.COMMA.getDelimiter());
                double minX = Double.valueOf(cs[0]).doubleValue();
                double maxX = Double.valueOf(cs[1]).doubleValue();
                double minY = Double.valueOf(cs[2]).doubleValue();
                double maxY = Double.valueOf(cs[3]).doubleValue();
                Envelope envelope = new Envelope(minX,maxX,minY,maxY);
                r.add(new Pair<>(id,envelope));
            }
            return r;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }


}
